﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Altairis.Web.UI.WebControls {

    [Themeable(true)]
    [TargetControlType(typeof(IPageableItemContainer))]
    public class DataPagerExtender : Control {
        private const string DEFAULT_COLLECTION_KEY = "PageIndex";
        private const int DEFAULT_PAGE_SIZE = 10;
        private const string DESIGN_TIME_HTML = "<table cellpadding=4 cellspacing=0 style=\"font:messagebox;color:buttontext;background-color:buttonface;border: solid 1px;border-top-color:buttonhighlight;border-left-color:buttonhighlight;border-bottom-color:buttonshadow;border-right-color:buttonshadow\"><tr><td nowrap><span style=\"font-weight:bold\">{0}</span> - {1}</td></tr><tr><td>{2}</td></tr></table>";

        public DataPagerExtender() {
            this.PageSize = DEFAULT_PAGE_SIZE;
            this.CollectionKey = DEFAULT_COLLECTION_KEY;
            this.InactiveLinkMode = InactiveLinkMode.Ignore;
            this.PageIndexSource = PageIndexSource.RouteParameter;
        }

        #region Configuration properties

        [Category("Behavior"), Description("Name of key in collection to read current page index from. Collection is specified by PageIndexSource property.")]
        [DefaultValue(DEFAULT_COLLECTION_KEY), Themeable(true)]
        public string CollectionKey {
            get { return (string)this.ViewState["CollectionKey"]; }
            set { this.ViewState["CollectionKey"] = value; }
        }

        [Category("Paging Controls"), Description("ID of HyperLink control pointing to first page.")]
        [IDReferenceProperty(typeof(HyperLink))]
        public string FirstLinkID {
            get { return (string)this.ViewState["FirstLinkID"]; }
            set { this.ViewState["FirstLinkID"] = value; }
        }

        [Category("Behavior"), Description("Defines what should happen with links being inactive in given context.")]
        [DefaultValue(InactiveLinkMode.Ignore), Themeable(true)]
        public InactiveLinkMode InactiveLinkMode {
            get { return (InactiveLinkMode)this.ViewState["InactiveLinkMode"]; }
            set { this.ViewState["InactiveLinkMode"] = value; }
        }

        [Category("Paging Controls"), Description("ID of ITextControl with paging info. Sample text with placeholder is: \"Items {0}-{1} of {2} total, page {3} of {4}\".")]
        [IDReferenceProperty(typeof(ITextControl))]
        public string InfoLabelID {
            get { return (string)this.ViewState["InfoLabelID"]; }
            set { this.ViewState["InfoLabelID"] = value; }
        }

        [Category("Paging Controls"), Description("ID of HyperLink control pointing to last page.")]
        [IDReferenceProperty(typeof(HyperLink))]
        public string LastLinkID {
            get { return (string)this.ViewState["LastLinkID"]; }
            set { this.ViewState["LastLinkID"] = value; }
        }

        [Category("Paging Controls"), Description("ID of HyperLink control pointing to next page.")]
        [IDReferenceProperty(typeof(HyperLink))]
        public string NextLinkID {
            get { return (string)this.ViewState["NextLinkID"]; }
            set { this.ViewState["NextLinkID"] = value; }
        }

        [Category("Behavior"), Description("Defines where the current page index is to be read from.")]
        [DefaultValue(PageIndexSource.RouteParameter), Themeable(true)]
        public PageIndexSource PageIndexSource {
            get { return (PageIndexSource)this.ViewState["PageIndexSource"]; }
            set { this.ViewState["PageIndexSource"] = value; }
        }

        [Category("Behavior"), Description("Maximum number of items on page.")]
        [DefaultValue(DEFAULT_PAGE_SIZE), Themeable(true)]
        public int PageSize {
            get { return (int)this.ViewState["PageSize"]; }
            set { this.ViewState["PageSize"] = value; }
        }

        [Category("Paging Controls"), Description("ID of HyperLink control pointing to previous page.")]
        [IDReferenceProperty(typeof(HyperLink))]
        public string PreviousLinkID {
            get { return (string)this.ViewState["PreviousLinkID"]; }
            set { this.ViewState["PreviousLinkID"] = value; }
        }

        [Category("Behavior"), Description("ID of the IPageableItemContainer to be paged.")]
        [IDReferenceProperty(typeof(IPageableItemContainer))]
        public string TargetControlID {
            get { return (string)this.ViewState["TargetControlID"]; }
            set { this.ViewState["TargetControlID"] = value; }
        }

        #endregion Configuration properties

        [Browsable(false)]
        public int PageIndex {
            get {
                string pageIndexString;
                if (this.PageIndexSource == PageIndexSource.QueryString) {
                    pageIndexString = this.Page.Request.QueryString[this.CollectionKey];
                }
                else {
                    pageIndexString = this.Page.RouteData.Values[this.CollectionKey] as string;
                }
                if (string.IsNullOrEmpty(pageIndexString)) pageIndexString = "1";
                return int.Parse(pageIndexString);
            }
        }

        [Browsable(false)]
        public int StartRowIndex {
            get {
                return (this.PageIndex - 1) * this.PageSize;
            }
        }

        [Browsable(false)]
        public int TotalPages {
            get {
                return (int)Math.Ceiling(this.TotalRowCount / (decimal)this.PageSize);
            }
        }

        [Browsable(false)]
        public int TotalRowCount { get; private set; }

        [Browsable(false)]
        [IDReferenceProperty(typeof(IPageableItemContainer))]
        protected IPageableItemContainer TargetControl { get; set; }

        protected TControl FindRelatedControl<TControl>(string controlId) where TControl : class {
            if (string.IsNullOrEmpty(controlId)) return null;
            var control = this.NamingContainer.FindControl(controlId) as TControl;
            if (control == null) control = (this.TargetControl as Control).FindControl(controlId) as TControl;
            return control;
        }

        protected string GetUrlForPage(int pageIndex) {
            if (this.PageIndexSource == PageIndexSource.RouteParameter) {
                // Get URL via routing engine
                var dict = new System.Web.Routing.RouteValueDictionary(this.Page.RouteData.Values);
                dict[this.CollectionKey] = pageIndex.ToString();
                return "~/" + this.Page.RouteData.Route.GetVirtualPath(this.Page.Request.RequestContext, dict).VirtualPath;
            }
            else {
                // Setup new query string values
                var qs = new NameValueCollection(this.Page.Request.QueryString);
                if (pageIndex == 1) qs.Remove(this.CollectionKey);
                else qs.Set(this.CollectionKey, pageIndex.ToString());

                // Construct new URL using modified query string values
                var qsb = new System.Text.StringBuilder(this.Page.Request.AppRelativeCurrentExecutionFilePath);
                qsb.Append("?");
                foreach (var key in qs.AllKeys) {
                    qsb.AppendFormat("{0}={1}&", key, HttpUtility.UrlEncode(qs[key]));
                }
                var url = qsb.ToString();
                url = url.TrimEnd('?', '&');
                return url;
            }
        }

        protected override void OnLoad(EventArgs e) {
            base.OnLoad(e);

            // Find target control
            this.TargetControl = this.NamingContainer.FindControl(this.TargetControlID) as IPageableItemContainer;
            if (this.TargetControl == null) throw new ArgumentException("Target control does not exist or does not implement IPageableItemContainer.");

            // Setup paging
            this.TargetControl.SetPageProperties(this.StartRowIndex, this.PageSize, true);

            // Get total rows when available
            this.TargetControl.TotalRowCountAvailable += new EventHandler<PageEventArgs>(TargetControl_TotalRowCountAvailable);
        }

        protected override void Render(HtmlTextWriter writer) {
            if (!this.DesignMode) return;
            writer.Write(DESIGN_TIME_HTML, "DataPagerExtender", this.ID, "");
        }

        protected void SetupPagingLinkControl(string controlId, int pageIndex) {
            // Do nothing when link is to be ignored and is inactive
            if (pageIndex == 0 && this.InactiveLinkMode == InactiveLinkMode.Ignore) return;

            // Find link control
            var link = FindRelatedControl<HyperLink>(controlId);
            if (link == null) return;

            if (pageIndex == 0) {
                // Link is inactive
                if (this.InactiveLinkMode == InactiveLinkMode.Hide) link.Visible = false;
                else { link.Enabled = false; }
                return;
            }

            // Link is active - get page URL
            link.NavigateUrl = this.GetUrlForPage(pageIndex);
        }

        protected void SetupUI() {
            // Setup paging label
            // Sample format string: "Items {0}-{1} of {2} total, page {3} of {4}"
            var infoControl = this.FindRelatedControl<ITextControl>(this.InfoLabelID);
            if (infoControl != null) {
                infoControl.Text = string.Format(infoControl.Text,
                    this.StartRowIndex + 1,             // 0
                    this.StartRowIndex + this.PageSize, // 1
                    this.TotalRowCount,                 // 2
                    this.PageIndex,                     // 3
                    this.TotalPages);                   // 4
            }

            // Setup paging links
            if (this.PageIndex == 1) {
                this.SetupPagingLinkControl(this.FirstLinkID, 0);
                this.SetupPagingLinkControl(this.PreviousLinkID, 0);
            }
            else {
                this.SetupPagingLinkControl(this.FirstLinkID, 1);
                this.SetupPagingLinkControl(this.PreviousLinkID, this.PageIndex - 1);
            }
            if (this.PageIndex == this.TotalPages) {
                this.SetupPagingLinkControl(this.NextLinkID, 0);
                this.SetupPagingLinkControl(this.LastLinkID, 0);
            }
            else {
                this.SetupPagingLinkControl(this.NextLinkID, this.PageIndex + 1);
                this.SetupPagingLinkControl(this.LastLinkID, this.TotalPages);
            }
        }

        protected void TargetControl_TotalRowCountAvailable(object sender, PageEventArgs e) {
            // Get total row count
            this.TotalRowCount = e.TotalRowCount;

            // Setup paging links
            this.SetupUI();
        }
    }
}